#虽然底层提供了版本对比的方法，但是为了防止有其他的包用到了这里的方法所以选择翻译
import string
from enum import Enum
#// Using documentation from: http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version

#// CompareVersions compares two package versions
def CompareVersions(ver1, ver2 ):
    e1, u1, d1 = parseVersion(ver1)
    e2, u2, d2 = parseVersion(ver2)
    
    r = compareVersionPart(e1, e2)
    if r != 0 :
        return r
    

    r = compareVersionPart(u1, u2)
    if r != 0 :
        return r

    return compareVersionPart(d1, d2)


#// parseVersions breaks down full version to components (possibly empty)
def parseVersion(ver ) :
    i = ver.rfind("-")
    debian=''
    if i != -1 :
        debian, ver = ver[i+1:], ver[:i]
    
    i = ver.find(":")
    epoch=''
    if i != -1 :
        epoch, ver = ver[:i], ver[i+1:]
    

    upstream = ver

    return (epoch,upstream,debian)


#// compareLexicographic compares in "Debian lexicographic" way, see below compareVersionPart for details
def compareLexicographic(s1, s2 ) :
    i = 0
    l1, l2 = len(s1), len(s2)

    while True:
        if i == l1 and i == l2 :
            # // s1 equal to s2
            break
        

        if i == l2 :
            # // s1 is longer than s2
            if s1[i] == '~' :
                return -1 # s1 < s2
            
            return 1 # s1 > s2
        

        if i == l1 :
            # s2 is longer than s1
            if s2[i] == '~' :
                return 1 # s1 > s2
            
            return -1 # s1 < s2
        

        if s1[i] == s2[i] :
            i+=1
            continue
        

        if s1[i] == '~' :
            return -1
        

        if s2[i] == '~' :
            return 1
        

        c1, c2 = s1[i] in string.ascii_letters, s2[i] in string.ascii_letters
        if c1 and not c2 :
            return -1
        
        if not c1 and c2 :
            return 1
        

        if s1[i] < s2[i] :
            return -1
        
        return 1
    
    return 0

# // compareVersionPart compares parts of full version
# //
# // From Debian Policy Manual:
# //
# // "The strings are compared from left to right.
# //
# // First the initial part of each string consisting entirely of non-digit characters is
# // determined. These two parts (one of which may be empty) are compared lexically. If a
# // difference is found it is returned. The lexical comparison is a comparison of ASCII values
# // modified so that all the letters sort earlier than all the non-letters and so that a tilde
# // sorts before anything, even the end of a part. For example, the following parts are in sorted
# // order from earliest to latest: ~~, ~~a, ~, the empty part.
# //
# // Then the initial part of the remainder of each string which consists entirely of digit
# // characters is determined. The numerical values of these two parts are compared, and any difference
# // found is returned as the result of the comparison. For these purposes an empty string (which can only occur at
# // the end of one or both version strings being compared) counts as zero.

# // These two steps (comparing and removing initial non-digit strings and initial digit strings) are
# // repeated until a difference is found or both strings are exhausted."
def compareVersionPart(part1, part2 ) :
    i1, i2 = 0, 0
    l1, l2 = len(part1), len(part2)

    while True:
        j1, j2 = i1, i2
        while j1 < l1 and (part1[j1] not in string.digits) :
            j1+=1
        

        while j2 < l2 and (part2[j2] not in string.digits) :
            j2+=1
        

        s1, s2 = part1[i1:j1], part2[i2:j2]

        r = compareLexicographic(s1, s2)
        
        if r != 0 :
            return r
        

        i1, i2 = j1, j2

        while j1 < l1 and (part1[j1]  in string.digits) :
            j1+=1
        

        while j2 < l2 and (part2[j2]  in string.digits) :
            j2+=1
        

        s1, s2 = part1[i1:j1], part2[i2:j2]
        if len(s1)==0:
            s1='0'
        if len(s2)==0:
            s2='0'
        n1= int(s1)
        n2= int(s2)

        if n1 < n2 :
            return -1
        
        if n1 > n2 :
            return 1
        

        i1, i2 = j1, j2

        if i1 == l1 and i2 == l2 :
            break
        

    return 0


# # // Version relations
class VersionRelation(Enum):
    VersionDontCare = 0
    VersionLess="<<"
    VersionLessOrEqual=2
    VersionEqual="="
    VersionGreaterOrEqual=">="
    VersionGreater=">>"
    VersionPatternMatch="%"
    VersionRegexp="~"


# # // Dependency is a parsed version of Debian dependency to package
class Dependency :
    Pkg          :str=''
    Relation     :int=0
    Version      :str=''
    Architecture :str=''
    Regexp       =None


    # // Hash calculates some predefined unique ID of Dependency
    def Hash(d) ->str:
        return "{}:{}:{}:{}".format( d.Architecture, d.Pkg, d.Relation, d.Version)


    # // String produces human-readable representation
    def  String(d) ->str:
        # var rel string
        if d.Relation==VersionRelation.VersionDontCare:
            return "{} [{}]".format( d.Pkg, d.Architecture)
        rel=VersionRelation[ d.Relation] 

        return "{} ({} {}) [{}]".format(d.Pkg, rel, d.Version, d.Architecture)
    

# // ParseDependencyVariants parses dependencies in format "pkg (>= 1.35) | other-package"
def ParseDependencyVariants(variants :str):
    parts = variants.split( "|")
    l = []

    for part in parts :
        l.append(ParseDependency(part.strip()))
    return l

# // ParseDependencyArch parses the dependency name in format "pkg:any" into name and architecture
def ParseDependencyArch(d :Dependency) :
    if d.Pkg.find( ':')!=-1 :
        parts = d.Pkg( ":", 2)
        d.Pkg, d.Architecture = parts[0], parts[1]
        if d.Architecture == "any" :
            d.Architecture = ""
        

# # // ParseDependency parses dependency in format "pkg (>= 1.35) [arch]" into parts
def ParseDependency(dep :str,d :Dependency) -> Dependency:
    if dep.endswith( "}") :
        i = dep.rfind( "{")
        if i == -1 :
            print("Error:unable to parse dependency: %s", dep)
            return
        
        d.Architecture = dep[i+1 : len(dep)-1]

        dep = dep[:i].strip()

    if not dep.endswith( ")") :
        d.Pkg = dep.strip()
        d.Relation = VersionRelation.VersionDontCare
        ParseDependencyArch(d)
        return d

    i = dep.find( "(")
    if i == -1 :
        print("Error:unable to parse dependency: %s", dep)
        return
    

    d.Pkg = dep[0:i].strip()
    ParseDependencyArch(d)

    rel = ""
    if dep[i+1] == '>' or dep[i+1] == '<' or dep[i+1] == '=' :
        rel += dep[i+1 : i+2]
        if dep[i+2] == '>' or dep[i+2] == '<' or dep[i+2] == '=' :
            rel += dep[i+2 : i+3]
            d.Version = dep[i+3 : len(dep)-1].strip()
        else :
            d.Version = (dep[i+2 : len(dep)-1]).strip()
        
    else :
        d.Version = (dep[i+1 : len(dep)-1]).strip()
    

    
    if rel== "<" or rel== "<=":
        d.Relation = VersionRelation.VersionLessOrEqual
    elif rel== ">" or rel== ">=":
        d.Relation = VersionRelation.VersionGreaterOrEqual
    elif rel== "<<":
        d.Relation = VersionRelation.VersionLess
    elif rel==">>":
        d.Relation = VersionRelation.VersionGreater
    elif rel=="" or rel== "=":
        d.Relation = VersionRelation.VersionEqual
    else:
        print("Error:relation unknown %#v in dependency %s", rel, dep)
    

    return